package com.trip.api;

import java.util.Collection;
import java.util.Map;

import com.trip.exception.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

public class ControllerResult<T> {
	private static final Logger logger = LoggerFactory.getLogger(ControllerResult.class);

	private T result;
	private String errmsg;
	private Boolean success = true;
	private String errDetail;

	private ControllerResult() {
	}

	public T getResult() {
		return result;
	}

	public void setResult(T result) {
		this.result = result;
	}

	public String getErrmsg() {
		return errmsg;
	}

	public void setErrmsg(String errmsg) {
		this.errmsg = errmsg;
	}

	public String getErrDetail() {
		return errDetail;
	}

	public void setErrDetail(String errDetail) {
		this.errDetail = errDetail;
	}

	public Boolean getSuccess() {
		return success;
	}

	public void setSuccess(Boolean success) {
		this.success = success;
	}

	public static <T> ControllerResult<T> success() {
		ControllerResult<T> controllerResult = new ControllerResult<>();
		controllerResult.setSuccess(true);
		controllerResult.setResult(null);

		logger.debug("Success with result {}", toString(controllerResult));
		return controllerResult;
	}

	public static <T> ControllerResult<T> success(T result) {
		ControllerResult<T> controllerResult = new ControllerResult<>();
		controllerResult.setResult(result);

		logger.debug("Success with result {}", toString(result));
		return controllerResult;
	}

	public static <T> ControllerResult<T> success(T result, String message) {
		ControllerResult<T> controllerResult = new ControllerResult<>();
		controllerResult.setErrmsg(message);
		controllerResult.setResult(result);

		logger.debug("Success with result {} and message {}", toString(result), message);
		return controllerResult;
	}

	public static <T> ControllerResult<T> failed() {
		ControllerResult<T> controllerResult = new ControllerResult<>();
		controllerResult.setSuccess(false);

		logger.debug("Failed with no message.");
		return controllerResult;
	}

	public static <T> ControllerResult<T> failed(String message) {
		ControllerResult<T> controllerResult = new ControllerResult<>();
		controllerResult.setErrmsg(message);
		controllerResult.setSuccess(false);

		logger.debug("Failed with message {}", message);
		return controllerResult;
	}

	public static <T> ControllerResult<T> failed(HttpStatus status, String message) {
		ControllerResult<T> controllerResult = new ControllerResult<>();
		controllerResult.setErrmsg(message);
		controllerResult.setSuccess(false);

		logger.debug("Failed with message {} and http status {}", message, status);
		return controllerResult;
	}

	public static <T> ControllerResult<T> failed(Throwable e) {
		if (e instanceof ServiceException) {
			return failed((ServiceException) e);
		}else if(e instanceof  MethodArgumentNotValidException){
			return failed((MethodArgumentNotValidException) e);
		}else if(e instanceof  ConstraintViolationException){
			return failed((ConstraintViolationException) e);
		}
		ControllerResult<T> controllerResult = new ControllerResult<>();
		if(e instanceof MethodArgumentTypeMismatchException){
			controllerResult.setErrmsg("Server busy, please retry later");
			controllerResult.setErrDetail(e.getMessage());
		}else {
			controllerResult.setErrmsg("Server busy, please retry later");
			controllerResult.setErrDetail(e.getMessage());
		}
		controllerResult.setSuccess(false);

		logger.warn("Failed with throwable exception, message {}", e.getMessage());
		logger.debug("Failed with throwable exception, stack trace:", e);
		return controllerResult;
	}

	public static <T> ControllerResult<T> failed(MethodArgumentNotValidException e){
		ControllerResult<T> controllerResult = new ControllerResult<>();

		for(ObjectError oe: e.getBindingResult().getAllErrors()){
			controllerResult.setErrmsg(oe.getDefaultMessage());
			break;
		}

		controllerResult.setSuccess(false);

		logger.warn("Failed with base exception, message {}", e.getMessage());
//		logger.debug("Failed with base exception, stack trace:", e);
		return controllerResult;
	}

	public static <T> ControllerResult<T> failed(ConstraintViolationException e){
		ControllerResult<T> controllerResult = new ControllerResult<>();

		for(ConstraintViolation cv: e.getConstraintViolations()){
			controllerResult.setErrmsg(cv.getMessage());
			break;
		}

		controllerResult.setSuccess(false);

		logger.warn("Failed with base exception, message {}", e.getMessage());
//		logger.debug("Failed with base exception, stack trace:", e);
		return controllerResult;
	}

	public static <T> ControllerResult<T> failed(ServiceException e) {
		ControllerResult<T> controllerResult = new ControllerResult<>();
		controllerResult.setErrmsg(e.getMessage());
		controllerResult.setSuccess(false);

		logger.warn("Failed with base exception, message {}", e.getMessage());
//		logger.debug("Failed with base exception, stack trace:", e);
		return controllerResult;
	}



	@SuppressWarnings("rawtypes")
	public static <T> String toString(T value) {
		if (value == null) {
			return "<Empty>";
		} else if (value instanceof Collection) {
			return "Collection with size " + ((Collection) value).size();
		} else if (value instanceof Map) {
			return "Map with size " + ((Map) value).size();
		}
		return String.valueOf(value);
	}

}